function getServerURL() {
    var serverURL = '';
    if (typeof app !== 'undefined' && app && typeof app == 'object' && (app?.getSetting || false) && typeof app.getSetting === 'function') {
        serverURL = app.getSetting('server_url') || '';
        if (serverURL.endsWith('/')) serverURL = serverURL.slice(0, -1);
    }
    return serverURL;
}
function url2embed(url) {
    if (typeof url != 'string' || !url.toLowerCase().startsWith('https://')) {
        return false;
    }
    try {
        var domain = new URL(url).hostname;
        if (!domain || typeof domain != 'string') {
            return false;
        }
        domain = domain.toLowerCase();
        if (domain.startsWith('www.')) domain = domain.slice(4);
        switch (domain) {
            // Known no-embed domains
            case 'twitter.com':
            case 'x.com':
                return false;
            // Requires embed URL
            case 'youtube.com':
            case 'youtube.be':
                const videoId = url.split('v=')[1];
                const ampersandPosition = videoId.indexOf('&');
                if (ampersandPosition !== -1) {
                    return `https://www.youtube.com/embed/${videoId.substring(0, ampersandPosition)}`;
                }
            // Default: return URL
            default: return url;
        }
    } catch (e) {
        return false;
    }
}
function render_threads(server_url = '', exit_callback = null) {
    $('.thread').each(function() {
        const thread = $(this);
        const skip_link = thread.hasClass('skip-link');
        const target_blank = (typeof server_url === 'string' && server_url.length > 0) ? ' target="_blank"' : '';

        // Text fields
        const content = thread.attr('data-content') || '';
        const url = thread.attr('data-url') || '';
        const channel = thread.attr('data-channel') || '';
        const channelId = thread.attr('data-channel-id') || 0;
        const replyCount = thread.attr('data-reply-count') || 0;
        const threadType = thread.attr('data-thread-type') || '';

        // IDs 
        const chatId = thread.attr('data-chat-id') || '';
        const topChatId = thread.attr('data-top-chat-id') || '';
        const authorPublicKey = thread.attr('data-author-public-key') || '';
        const authorDisplayName = thread.attr('data-author-display-name') || '';
        const authorAlias = thread.attr('data-author-alias') || '';
        const authorPic = thread.attr('data-author-pic') || '';
        const datetimeCreated = thread.attr('data-datetime-created') || '';

        // Build HTML - use top_chat_id for navigation if available, otherwise fall back to chat_id
        const navigationId = topChatId || chatId;
        const chat_page = `${server_url}/thread/${navigationId}`;
        const channel_page = channelId ? `${server_url}/channel/${channelId}` : null;
        var replyStr = '';
        if (replyCount && !isNaN(replyCount * 1) && replyCount * 1) {
            replyStr = `<span><span class="text-muted small thread_reply_ticker">${replyCount}</span>&nbsp;${bsi('chat-fill')}</span>`;
        }
        var chat_link = `<a href="${chat_page}"${target_blank} class="link_to_thread" data-chat-id="${chatId}" data-author-public-key="${authorPublicKey}">${chatId}</a>`;
        if (skip_link) {
            chat_link = `<strong>${chatId}</strong>`;
        }
        var content_link = `<a href="${chat_page}"${target_blank} class="link_to_thread content_link" data-chat-id="${chatId}" data-author-public-key="${authorPublicKey}">${content}</a>`;
        if (skip_link) {
            content_link = content;
        }
        var exit_link = '';
        if (exit_callback && typeof exit_callback == 'function') {
            exit_link = `<a class="go-back-link" href="/" style="padding-right:10px;"><span class="bi bi-x-lg"></span></a>`;
        }
        var picImg = '';
        if (authorPic && typeof authorPic == 'string' && authorPic.length > 0 && authorPic.startsWith('http')) {
            picImg = `<img src="${authorPic}" class="rounded-circle" style="width:20px;height:20px;margin-right:5px;">`;
        }
        var aliasClass = '';
        var profile_page = `${server_url}/profile/${authorPublicKey}`;
        if (authorAlias && typeof authorAlias == 'string' && authorAlias.length > 0) {
            aliasClass = 'has_alias';
            profile_page = `${server_url}/profile/${authorAlias}`;
        } else {
            aliasClass = 'no_alias';
        }
        thread.empty().append(`
             <small class="text-muted">
                 ${exit_link}${chat_link} by
                 <a href="${profile_page}"${target_blank} class="${aliasClass} user-profile-link" data-public-key="${authorPublicKey}">${picImg}${authorDisplayName.trim()}</a>
                 <span class="float-end text-muted">
                    ${replyStr}
                     <small class="text-muted relative-time" data-time="${datetimeCreated}">${datetimeCreated}</small>
                     <a class="chat-options-opener text-muted small" data-type="thread" data-chat-id="${chatId}" data-author-public-key="${authorPublicKey}">
                        ${bsi('three-dots-vertical')}
                     </a>
                 </span>
             </small>
             <br>
             <span class="thread-content-span">
                 ${(channel && channel_page) ? `<span class="badge rounded-pill bg-secondary"><a href="${channel_page}"${target_blank}>${channel}</a></span>` : ''} 
                 ${content_link}
             </span>
             ${threadType === 'creator_poll' ? `<div class="poll-loading" data-chat-id="${chatId}"><small class="text-muted">Loading poll...</small></div>` : ''}
             <div class="thread-iframe-container" style="display:none;max-height:30vh;width:100%;" data-chat-id="${chatId}">
             </div>
             <div class="reaction-container" data-chat-id="${chatId}">
                 <button class="btn btn-sm btn-outline-secondary text-muted" style="opacity:0.5;">
                     ⮝ ...
                  </button>
                 <button class="btn btn-sm btn-outline-secondary text-muted" style="opacity:0.5;">
                     ⮟ ...
                  </button>
             </div>
        `);

        if (exit_callback && typeof exit_callback == 'function') {
            exit_link = thread.find('.go-back-link');
            exit_link.on('click', exit_callback);
            render_bsi();
        }

        if (url) {
            const link_toggle = $(`<a class="thread-link-toggle" data-chat-id="${chatId}" title="Toggle URL visibility">🔗</a>`);
            link_toggle.on('click', function() {
                const linkContainer = $(this).siblings('.thread-link-container');
                linkContainer.toggle(200);
            });
            thread.find('.thread-content-span').append(
                link_toggle,
                `
                <div class="thread-link-container" style="display:none;" data-chat-id="${chatId}">
                <a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>
                </div>`
            );
            // Allow direct embedding if thread has class allow-direct-embed
            if (thread.hasClass('allow-direct-embed')) {
                const embed_url = url2embed(url);
                if (embed_url) {
                    const embed_toggle = $(`<a class="thread-embed-toggle" data-url="${embed_url}" data-chat-id="${chatId}" title="Toggle Embed visibility">📺</a>`);
                    embed_toggle.on('click', function() {
                        const chatId = $(this).attr('data-chat-id');
                        if ($(this).hasClass('remover')) {
                            $(this).removeClass('remover');
                            $(`.thread-iframe-container[data-chat-id="${chatId}"]`).slideUp(300, function() {
                                $(this).empty();
                            });
                            return;
                        }
                        const embed_url = $(this).attr('data-url');
                        const iframe = $(`<iframe src="${embed_url}" data-chat-id="${chatId}" style="width:100%;height:100%;border:none;outline:none;"></iframe>`);
                        $(`.thread-iframe-container[data-chat-id="${chatId}"]`).append(iframe);
                        iframe.on('load', (e) => {
                            const chatId = $(e.target).attr('data-chat-id');
                            $(`.thread-iframe-container[data-chat-id="${chatId}"]`).slideDown(300, () => {
                                $(`.thread-embed-toggle[data-chat-id="${chatId}"]`).addClass('remover');
                            });
                        }).on('error', (e) => {
                            const chatId = $(e.target).attr('data-chat-id');
                            $(`.thread-iframe-container[data-chat-id="${chatId}"]`).empty().append("<div class='alert alert-danger'>Failed to load embed</div>").slideDown(300);
                        })
                    });
                    thread.find('.thread-link-toggle').after(embed_toggle);
                }
            }
        }
        try {
            setTimeout(formatRelativeTime, 10);
        } catch (e) {
            console.error(e);
        }
    });

    // Update reactions (requires reactions.js)
    if (typeof loadBatchedReactions === 'function') {
        loadBatchedReactions();
    }

    // Add follow links (requires follow.js)
    init_follow_links(getServerURL());

    // Load poll data for poll threads (requires polls.js)
    if (typeof initPolls === 'function') {
        initPolls(server_url);
    }
}